Skip to main content

3.1 - The Heart of Your Bot

At the center of every project is your main bot class. This class is not just a container for code; it's the brain of your entire operation. To write effective bots, you must first understand the structure of this class and the powerful "API contract" it fulfills by inheriting from the library's BotAI.

Think of BotAI as the standard cockpit of a complex vehicle. It provides all the essential instruments, displays, and controls. Your job is to create a pilot—your bot class—that inherits this cockpit and uses it to make intelligent decisions.

The Inheritance Structure

[ burnysc2 Library ]                  [ Your Code: my_bot.py ]
+---------------------+
| BotAI |
|---------------------| +----------------------+
| - self.state | | MyBot |
| - self.game_info | <-----------+ |----------------------|
| - self.time | (Inherits) | + on_start() |
| - self.minerals | | + on_step() |
| - self.units | | + on_end() |
| ...and many more | +----------------------+
+---------------------+

By writing class MyBot(BotAI):, you are signing a contract. You agree to implement the logic, and in return, BotAI provides you with a rich set of tools and guarantees that certain methods in your class (on_start, on_step, etc.) will be called at the appropriate times.

The BotAI Contract: What You Get via self

Inheriting from BotAI gives your class access to a host of properties and methods through the self keyword. These can be grouped into two categories: Sensing (reading the game state) and Acting (executing logic at specific times).

CategoryComponentHow You Use It (Example)Its Purpose
SensingCore Data Objectsself.state / self.game_infoProvides the raw, unfiltered snapshot of all game data. This is the ultimate source of truth.
Convenience Helpersself.units, self.workers, self.enemy_structuresClean, readable shortcuts to filtered lists of units from self.state. Far easier to use.
Resource Propertiesself.minerals, self.vespene, self.supply_leftInstant, direct access to the most commonly needed resource values.
Time & Iterationself.time, self.time_formatted, iterationTrack game progress. self.time (in seconds) is best for time-based logic.
ActingLifecycle Methodsasync def on_start(self):A "hook" that runs once at the very beginning of the game. Perfect for initial setup.
async def on_step(self, iteration):The main game loop. Runs repeatedly (many times per second). Your core logic lives here.
async def on_end(self, result):A "hook" that runs once after the game has finished. Ideal for cleanup or analysis.
Event-Driven Methodson_unit_created, on_building_construction_completeMethods that run only when a specific, named event occurs in the game.

Code Example: A Smarter Skeleton

This example demonstrates how to properly use the BotAI contract to build a simple, readable bot skeleton. Note the use of self.time for reliable, time-based checks.

Checklist for This Bot:

  • Inherit from BotAI.
  • Use on_start for a one-time setup message.
  • Use on_step for continuous, looping logic.
  • Access helper properties like self.time, self.supply_used, and self.townhalls.
# smart_skeleton.py

from sc2.main import run_game
from sc2 import maps
from sc2.bot_ai import BotAI
from sc2.ids.unit_typeid import UnitTypeId # A list of all unit IDs
from sc2.player import Bot, Computer
from sc2.data import Difficulty, Race


class SmartBot(BotAI):
"""
A bot skeleton that demonstrates the correct use of the BotAI class.
"""

def __init__(self):
super().__init__()
# Initialize a variable to track the last time we printed status.
self.last_log_time = 0

async def on_start(self):
"""Called once at the start."""
print(f"Game started on map: {self.game_info.map_name}")

async def on_step(self, iteration: int):
"""Called on every game step. This is where the main logic lives."""

# Log current status every 5 seconds of game time.
# Using self.time is more reliable than using the iteration number.
if self.time - self.last_log_time >= 5:
self.last_log_time = self.time
supply_used = self.supply_used
supply_cap = self.supply_cap
townhall_count = self.townhalls.amount

print(
f"Time: {self.time_formatted} | Townhalls: {townhall_count} | Supply: {supply_used}/{supply_cap}"
)

# Example of a simple decision:
if supply_used > supply_cap * 0.7 and self.can_afford(
UnitTypeId.SUPPLYDEPOT
):
print("ACTION: Supply is low, should build a Supply Depot.")


if __name__ == "__main__":
run_game(
maps.get("AbyssalReefLE"),
[
Bot(Race.Terran, SmartBot()),
Computer(Race.Zerg, Difficulty.Easy),
],
realtime=True,
)